#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "rulelist.h"
#include "get_file_extension.h"
#include "methods.h"

#define RULE_ALLOC 5

#define ARRAY_SIZE(x) (sizeof (x) / sizeof (*x))

int allocate (rulelist_t *rulelist);

void rulelist_init (rulelist_t *rulelist)
{
	rulelist->rules = NULL;
	rulelist->size = 0;
	rulelist->used = 0;
}

void rulelist_free (rulelist_t *rulelist)
{
	size_t i;

	if (rulelist->rules) {
		for (i = 0; i < rulelist->used; i++) {
			if (rulelist->rules[i].request_handling_criteria == BY_FILE_EXTENSION) {
				stringlist_free (&(rulelist->rules[i].u.file_extensions));
			} else if (rulelist->rules[i].request_handling_criteria == BY_PATH) {
				pathlist_free (&(rulelist->rules[i].u.pathlist));
			}

			backend_list_free (&(rulelist->rules[i].backend_list));
		}

		free (rulelist->rules);
		rulelist->rules = NULL;
	}

	rulelist->size = 0;
	rulelist->used = 0;
}

int allocate (rulelist_t *rulelist)
{
	rule_t *rules;
	size_t size;

	if (rulelist->used == rulelist->size) {
		size = rulelist->size + RULE_ALLOC;
		rules = (rule_t *) realloc (rulelist->rules, size * sizeof (rule_t));
		if (!rules) {
			return -1;
		}

		rulelist->rules = rules;
		rulelist->size = size;
	}

	return 0;
}

int rulelist_load (rulelist_t *rulelist, const char *host_name, configuration_t *conf)
{
	const char *rule_name;
	const char *request_handler;
	const char *string;
	rule_t *rule;
	stringlist_t *file_extensions;
	pathlist_t *pathlist;
	int method;
	size_t i, j;

	/* Free previous configuration. */
	rulelist_free (rulelist);

	for (i = 0; ((rule_name = configuration_get_child (conf, i, "VirtualHosts", host_name, "RequestHandling", NULL)) != NULL); i++) {
		if (allocate (rulelist) < 0) {
			return -1;
		}

		rule = &(rulelist->rules[rulelist->used++]);

		file_extensions = &(rule->u.file_extensions);
		stringlist_init (file_extensions);

		pathlist = &(rule->u.pathlist);
		pathlist_init (pathlist);

		memset (&(rule->u.methods), 0, sizeof (rule->u.methods));

		backend_list_init (&rule->backend_list);

		request_handler = configuration_get_value (conf, "VirtualHosts", host_name, "RequestHandling", rule_name, "RequestHandler", NULL);
		if ((!request_handler) || (!*request_handler)) {
			fprintf (stderr, "Rule [%s:%s] doesn't have a request handler.\n", host_name, rule_name);
			return -1;
		}

		if (strcasecmp (request_handler, "local") == 0) {
			rule->request_handler = LOCAL_REQUEST_HANDLER;
		} else if (strcasecmp (request_handler, "reverse-proxy") == 0) {
			rule->request_handler = REVERSE_PROXY_REQUEST_HANDLER;
		} else if (strcasecmp (request_handler, "fastcgi") == 0) {
			rule->request_handler = FASTCGI_REQUEST_HANDLER;
		} else {
			fprintf (stderr, "[%s] is not a valid request handler.\n", request_handler);
			return -1;
		}

		if ((string = configuration_get_child (conf, 0, "VirtualHosts", host_name, "RequestHandling", rule_name, "FileExtensions", NULL)) != NULL) {
			rule->request_handling_criteria = BY_FILE_EXTENSION;
			if (stringlist_insert_string (file_extensions, string, 0) < 0) {
				return -1;
			}

			for (j = 1; ((string = configuration_get_child (conf, j, "VirtualHosts", host_name, "RequestHandling", rule_name, "FileExtensions", NULL)) != NULL); j++) {
				if (stringlist_insert_string (file_extensions, string, 0) < 0) {
					return -1;
				}
			}
		} else if ((string = configuration_get_child (conf, 0, "VirtualHosts", host_name, "RequestHandling", rule_name, "Path", NULL)) != NULL) {
			rule->request_handling_criteria = BY_PATH;
			if (pathlist_insert (pathlist, string, strlen (string)) < 0) {
				return -1;
			}

			for (j = 1; ((string = configuration_get_child (conf, j, "VirtualHosts", host_name, "RequestHandling", rule_name, "Path", NULL)) != NULL); j++) {
				if (pathlist_insert (pathlist, string, strlen (string)) < 0) {
					return -1;
				}
			}
		} else if ((string = configuration_get_child (conf, 0, "VirtualHosts", host_name, "RequestHandling", rule_name, "Methods", NULL)) != NULL) {
			rule->request_handling_criteria = BY_METHOD;
			if ((method = search_string (methods, ARRAY_SIZE (methods), strncmp, string, strlen (string))) < 0) {
				fprintf (stderr, "Invalid method [%s].\n", string);
				return -1;
			}

			rule->u.methods[method] = 1;

			for (j = 1; ((string = configuration_get_child (conf, j, "VirtualHosts", host_name, "RequestHandling", rule_name, "Methods", NULL)) != NULL); j++) {
				if ((method = search_string (methods, ARRAY_SIZE (methods), strncmp, string, strlen (string))) < 0) {
					fprintf (stderr, "Invalid method [%s].\n", string);
					return -1;
				}

				rule->u.methods[method] = 1;
			}
		} else {
			fprintf (stderr, "Rule [%s:%s] doesn't have a request handling criteria.\n", host_name, rule_name);
			return -1;
		}

		if (rule->request_handler != LOCAL_REQUEST_HANDLER) {
			if (backend_list_load (&rule->backend_list, conf, host_name, rule_name) < 0) {
				fprintf (stderr, "Couldn't load backend list for rule [%s:%s].\n", host_name, rule_name);
				return -1;
			}
		}
	}

	return 0;
}

rule_t *rulelist_match (rulelist_t *rulelist, http_connection_t *http_connection, const char **file_extension)
{
	rule_t *rule;
	size_t i;

	*file_extension = NULL;

	for (i = 0; i < rulelist->used; i++) {
		rule = &(rulelist->rules[i]);
		switch (rule->request_handling_criteria) {
			case BY_FILE_EXTENSION:
				/* If we haven't extracted the file extension yet... */
				if (!*file_extension) {
					/* If the path doesn't have file extension... */
					if ((*file_extension = get_file_extension (http_connection->path.data, http_connection->path.used - 1)) == NULL) {
						*file_extension = (const char *) -1;
					} else {
						if (stringlist_search_string (&(rule->u.file_extensions), *file_extension, NULL) != -1) {
							return rule;
						}
					}
				} else if (*file_extension != (const char *) -1) {
					if (stringlist_search_string (&(rule->u.file_extensions), *file_extension, NULL) != -1) {
						return rule;
					}
				}

				break;
			case BY_PATH:
				if (pathlist_exists (&(rule->u.pathlist), http_connection->path.data, http_connection->path.used - 1)) {
					return rule;
				}

				break;
			case BY_METHOD:
				if (rule->u.methods[http_connection->http_method]) {
					return rule;
				}

				break;
		}
	}

	return NULL;
}
